package org.dromara.web.service.impl;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.business.service.BusinessConfigService;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.XcxLoginBody;
import org.dromara.common.core.domain.model.XcxLoginUser;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.enums.UserType;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.redis.utils.CacheUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.pay.service.QrCodeService;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysOssService;
import org.dromara.system.service.ISysPermissionService;
import org.dromara.system.service.ISysTenantService;
import org.dromara.system.service.ISysUserService;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.utils.WxXcxUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 小程序认证策略
 *
 * @author Michelle.Chung
 */
@Slf4j
@Service("xcx" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class XcxAuthStrategy implements IAuthStrategy {

    private final ISysUserService userService;
    private final SysUserMapper userMapper;
    private final ISysTenantService tenantService;
    private final ISysPermissionService permissionService;
    private final BusinessConfigService configService;

    @Override
    public LoginVo login(String body, SysClient client) {
        XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
        ValidatorUtils.validate(loginBody);
        // xcxCode 为 小程序调用 wx.login 授权后获取
        String xcxCode = loginBody.getXcxCode();
        // 多个小程序识别使用
//        String appid = loginBody.getAppid();

        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
        WxXcxUtil.XcxLoginResp xcxLoginResp = WxXcxUtil.xcxLogin(xcxCode);
        //查询用户信息
        String openid = xcxLoginResp.getOpenid();
        String unionid = xcxLoginResp.getUnionid();
        // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
        // 为统一app和小程序端账号数据, 采用unionid登录
        SysUserVo user = this.loadUserByUnionid(xcxLoginResp, loginBody);
        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
        XcxLoginUser loginUser = new XcxLoginUser();
        loginUser.setTenantId(user.getTenantId());
        loginUser.setUserId(user.getUserId());
        loginUser.setUsername(user.getUserName());
        loginUser.setNickname(user.getNickName());
        loginUser.setUserType(user.getUserType());
        loginUser.setClientKey(client.getClientKey());
        loginUser.setDeviceType(client.getDeviceType());
        loginUser.setOpenid(openid);
        loginUser.setUnionid(unionid);
        loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
        loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
        loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
        List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
        loginUser.setRoles(roles);
        SaLoginModel model = new SaLoginModel();
        model.setDevice(client.getDeviceType());
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
        // 例如: 后台用户30分钟过期 app用户1天过期
        model.setTimeout(client.getTimeout());
        model.setActiveTimeout(client.getActiveTimeout());
        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
        // 生成token
        LoginHelper.login(loginUser, model);

        LoginVo loginVo = new LoginVo();
        loginVo.setAccessToken(StpUtil.getTokenValue());
        loginVo.setExpireIn(StpUtil.getTokenTimeout());
        loginVo.setClientId(client.getClientId());
        loginVo.setOpenid(openid);
        loginVo.setUnionid(unionid);
        return loginVo;
    }

    private SysUserVo loadUserByUnionid(WxXcxUtil.XcxLoginResp xcxLoginResp, XcxLoginBody loginBody) {
        // 使用 unionid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
        SysUserVo user = userMapper.selectUserByOpenid(xcxLoginResp.getOpenid());
        //用户不存在
        if (ObjectUtil.isNull(user)) {
            //需要绑定抛错
            if (loginBody.getIsBindPhone()) {
                //未绑定unionid
                CacheUtils.put(CacheNames.XCX_LOGIN_RESP, loginBody.getXcxCode(), xcxLoginResp);
                throw new ServiceException(loginBody.getXcxCode(), 402);
            }
            if (TenantHelper.isEnable()) {
                if (!tenantService.checkAccountBalance(loginBody.getTenantId())) {
                    throw new ServiceException("当前租户下用户名额不足，请联系管理员");
                }
            }
            //创建用户
            this.buildUserBo(xcxLoginResp.getOpenid(), xcxLoginResp.getUnionid(), loginBody);
            user = userMapper.selectUserByOpenid(xcxLoginResp.getOpenid());
        }
        //防止无手机号脏数据导致意外报错
        if (loginBody.getIsBindPhone() && StrUtil.isBlank(user.getPhonenumber())) {
            CacheUtils.put(CacheNames.XCX_LOGIN_RESP, loginBody.getXcxCode(), xcxLoginResp);
            throw new ServiceException(loginBody.getXcxCode(), 402);
        }
        //用户已被停用 业务逻辑自行实现
        if (StrUtil.isNotBlank(user.getStatus())) {
            if (ObjectUtil.equal(UserStatus.DISABLE.getCode(), user.getStatus())) {
                throw new ServiceException("账号已禁用,请联系管理员");
            }
        }
        return user;
    }
    private final QrCodeService qrCodeService;
    private final ISysOssService ossService;
    @NotNull
    private SysUserBo buildUserBo(String openid, String unionid, XcxLoginBody loginBody) {
        SysUserBo userBo = new SysUserBo();
        userBo.setOpenid(openid);
        userBo.setUnionid(unionid);
        userBo.setTenantId(loginBody.getTenantId());
        userBo.setRoleIds(new Long[]{UserConstants.FRONT_ROLE_ID});
        userBo.setStatus(UserStatus.OK.getCode());
        userBo.setUserType(UserType.XCX_USER.getUserType());
        userBo.setNickName("用户" + RandomUtil.randomNumbers(6));
        userBo.setDeptId(104L);
        userBo.setPostIds(new Long[]{4L});
        userBo.setNumberDraws(Integer.valueOf(configService.queryValueByKey("number_draws")));
        TenantHelper.ignore(() -> DataPermissionHelper.ignore(() -> userService.insertUser(userBo)));

        byte[] qrCodeByte = qrCodeService.xcxGetQrCode(userBo.getUserId().toString(), "pages/index/index");
        SysOssVo upload = ossService.upload(qrCodeByte);
        TenantHelper.ignore(() -> DataPermissionHelper.ignore(() ->
            userService.lambdaUpdate().set(SysUser::getQrCode, upload.getOssId()).eq(SysUser::getUserId, userBo.getUserId()).update()
        ));
        return userBo;
    }

}
